1 module geany_dlang.plugin; 2 3 import geany_d_binding.geany.plugins; 4 import geany_d_binding.geany.types; 5 import geany_dlang.dcd_wrapper; 6 import geany_dlang.config: ConfigFile; 7 import logger; 8 import geany_d_binding.geany.sciwrappers; 9 import std.conv: to; 10 import dcd.common.messages; 11 import geany_d_binding.geany.document; 12 import geany_d_binding.geany.filetypes; 13 import geany_d_binding.scintilla.types; 14 import geany_d_binding.scintilla.Scintilla; 15 import geany_d_binding.scintilla.ScintillaGTK; 16 import std.string: toStringz; 17 18 private GeanyPlugin* geany_plugin; 19 package DcdWrapper wrapper; 20 ConfigFile configFile; 21 22 void init_keybindings() nothrow 23 { 24 import geany_d_binding.geany.pluginutils; 25 import geany_d_binding.geany.keybindings; 26 import gdk.Gdk: GdkModifierType; 27 import gtk.Widget: GtkWidget; 28 29 const gsize COUNT_KB = 2; 30 31 GeanyKeyGroup* key_group = plugin_set_key_group( 32 geany_plugin, 33 "dlang_keys", 34 COUNT_KB, 35 null // GeanyKeyGroupCallback 36 ); 37 38 const gint KB_COMPLETE_IDX = 0; 39 const guint KEY = 0; 40 41 keybindings_set_item( 42 key_group, 43 KB_COMPLETE_IDX, 44 &force_completion, 45 KEY, 46 cast(GdkModifierType) 0, 47 "exec", 48 "Display autocompletion dialog", 49 null // GtkWidget* 50 ); 51 52 keybindings_set_item( 53 key_group, 54 1, 55 &show_debug, 56 KEY, 57 cast(GdkModifierType) 0, 58 "exec 2", 59 "Dump debug info into log", 60 null // GtkWidget* 61 ); 62 } 63 64 void addCurrDocumentDirIntoImport(GeanyDocument* doc) nothrow 65 { 66 nothrowLog!"trace"(__FUNCTION__); 67 68 if(doc != null && doc.file_type.id == GeanyFiletypeID.GEANY_FILETYPES_D) 69 { 70 import std.path; 71 72 string filename = doc.file_name.to!string; 73 string path = dirName(filename); 74 75 try 76 { 77 nothrowLog!"trace"("Begin adding import path "~path); 78 79 wrapper.addImportPaths([path.to!string]); 80 } 81 catch(Exception e) 82 nothrowLog!"warning"(e.msg); 83 } 84 } 85 86 void attemptDisplaySomeWindow() nothrow 87 { 88 GeanyDocument* doc = document_get_current(); 89 const res = calculateCompletion(doc); 90 91 if(res.completions.length > 0) 92 { 93 const currPos = doc.editor.sci.sci_get_current_position; 94 95 const separator = cast(char) scintilla_send_message( 96 doc.editor.sci, 97 Sci.SCI_AUTOCGETSEPARATOR, 98 null, 99 null 100 ); 101 102 with(CompletionType) 103 switch(res.completionType) 104 { 105 case identifiers: 106 attemptDisplayCompletionWindow(doc, res, separator, currPos); 107 break; 108 109 case calltips: 110 attemptDisplayTipWindow(doc, res, separator, currPos); 111 break; 112 113 default: 114 nothrowLog!"warning"("Unknown completionType "~res.completionType.to!string); 115 break; 116 } 117 } 118 } 119 120 void attemptDisplayCompletionWindow(GeanyDocument* doc, in AutocompleteResponse res, char separator, int currPos) nothrow 121 { 122 const wordStartPos = cast(size_t) scintilla_send_message( 123 doc.editor.sci, 124 Sci.SCI_WORDSTARTPOSITION, 125 cast(uptr_t) currPos, 126 cast(sptr_t) true 127 ); 128 129 string preparedList; 130 131 foreach(i, ref c; res.completions) 132 { 133 if(i != 0) 134 preparedList ~= separator; 135 136 preparedList ~= c.identifier; 137 138 switch(c.kind) 139 { 140 case 'k': 141 preparedList ~= "?1"; 142 break; 143 144 case 'v': 145 preparedList ~= "?2"; 146 break; 147 148 default: 149 break; 150 } 151 } 152 153 scintilla_send_message( 154 doc.editor.sci, 155 Sci.SCI_AUTOCSETORDER, 156 cast(uptr_t) Sc.SC_ORDER_PERFORMSORT, 157 null 158 ); 159 160 const size_t alreadyEnteredNum = currPos - wordStartPos; 161 nothrowLog!"trace"("alreadyEnteredNum="~alreadyEnteredNum.to!string); 162 163 scintilla_send_message( 164 doc.editor.sci, 165 Sci.SCI_AUTOCSHOW, 166 cast(uptr_t) alreadyEnteredNum, 167 cast(sptr_t) preparedList.toStringz 168 ); 169 } 170 171 void attemptDisplayTipWindow(GeanyDocument* doc, in AutocompleteResponse tips, char separator, int pos) nothrow 172 { 173 string str; 174 175 foreach(i, ref c; tips.completions) 176 { 177 if(i != 0) 178 str ~= separator; 179 180 str ~= c.definition; 181 } 182 183 scintilla_send_message( 184 doc.editor.sci, 185 Sci.SCI_CALLTIPSHOW, 186 cast(uptr_t) pos, 187 cast(sptr_t) str.toStringz 188 ); 189 } 190 191 AutocompleteResponse calculateCompletion(GeanyDocument* doc) nothrow 192 { 193 AutocompleteResponse ret; 194 195 if(doc != null && doc.file_type.id == GeanyFiletypeID.GEANY_FILETYPES_D) 196 { 197 auto sci = doc.editor.sci; 198 const textLen = sci.sci_get_length; 199 char* textBuff = sci.sci_get_contents(-1); 200 scope(exit) g_free(textBuff); 201 202 AutocompleteRequest req; 203 req.kind = RequestKind.autocomplete; 204 req.cursorPosition = sci.sci_get_current_position; 205 req.sourceCode = cast(ubyte[]) textBuff[0 .. textLen+1]; 206 207 ret = wrapper.doRequest(req); 208 } 209 210 return ret; 211 } 212 213 package void substituteDcdPaths(ref DcdWrapper wrapper, string[] paths) 214 { 215 wrapper.cache.clear(); 216 wrapper.addImportPaths = paths; 217 } 218 219 extern(System) nothrow: 220 221 import gtkc.gobjecttypes: GObject; 222 import geany_d_binding.geany.editor: GeanyEditor; 223 import geany_d_binding.scintilla.Scintilla: SCNotification, Msg; 224 225 gboolean on_editor_notify(GObject* object, GeanyEditor* editor, SCNotification* nt, gpointer data) 226 { 227 return configFile.config.useCharAddEvent ? 228 processEditorNotify(object, nt) : false; 229 } 230 231 private gboolean processEditorNotify(GObject* object, SCNotification* nt) 232 { 233 import geany_d_binding.geany.dialogs; 234 import gtkc.gtktypes: GtkMessageType; 235 236 with(Msg) 237 switch (nt.nmhdr.code) 238 { 239 case SCN_CHARADDED: 240 nothrowLog!"trace"("SCN_CHARADDED received"); 241 attemptDisplaySomeWindow(); 242 return true; 243 244 case SCN_KEY: 245 case SCN_UPDATEUI: 246 case SCN_MODIFIED: 247 case SCN_PAINTED: 248 case SCN_FOCUSIN: 249 case SCN_FOCUSOUT: 250 case SCN_ZOOM: 251 break; 252 253 default: 254 auto c = cast(Msg) nt.nmhdr.code; 255 nothrowLog!"trace"("Notify code="~c.to!string); 256 break; 257 } 258 259 return false; 260 } 261 262 void on_document_filetype_set(GObject* obj, GeanyDocument* doc, GeanyFiletype* filetype_old, gpointer user_data) 263 { 264 nothrowLog!"trace"(__FUNCTION__); 265 266 addCurrDocumentDirIntoImport(doc); 267 } 268 269 void show_debug(guint key_id) 270 { 271 nothrowLog!"info"( 272 "Import paths:\n"~wrapper.listImportPaths.to!string 273 ); 274 275 nothrowLog!"info"(wrapper.cache.symbolsAllocated.to!string~" symbols cached."); 276 } 277 278 void force_completion(guint key_id) 279 { 280 attemptDisplaySomeWindow(); 281 } 282 283 gboolean initPlugin(GeanyPlugin *plugin, gpointer pdata) 284 { 285 nothrowLog!"trace"(__FUNCTION__); 286 287 geany_plugin = plugin; 288 289 try 290 { 291 configFile = new ConfigFile(plugin.geany_data); 292 293 wrapper = new DcdWrapper(); 294 wrapper.substituteDcdPaths(configFile.config.additionalPaths); 295 } 296 catch(Exception e) 297 { 298 nothrowLog!"fatal"(e.msg); 299 300 return false; 301 } 302 303 init_keybindings(); 304 305 return true; 306 } 307 308 void cleanupPlugin(GeanyPlugin* plugin, gpointer pdata) 309 { 310 try 311 { 312 destroy(wrapper); 313 destroy(configFile); 314 } 315 catch(Exception e) 316 nothrowLog!"fatal"(e.msg); 317 } 318 319 private PluginCallback[] callbacks; 320 321 shared static this() 322 { 323 import gtkc.gobjecttypes: GCallback; 324 325 callbacks = 326 [ 327 PluginCallback("editor-notify", cast(GCallback) &on_editor_notify, false, null), 328 PluginCallback("document-filetype-set", cast(GCallback) &on_document_filetype_set, true, null), 329 PluginCallback(null, null, false, null) 330 ]; 331 } 332 333 void geany_load_module(GeanyPlugin *plugin) 334 { 335 import geany_dlang.config_window: configWindowDialog; 336 337 plugin.funcs._init = &initPlugin; 338 plugin.funcs.cleanup = &cleanupPlugin; 339 plugin.funcs.callbacks = &callbacks[0]; 340 plugin.funcs.configure = &configWindowDialog; 341 342 plugin.info.name = "D language"; 343 plugin.info.description = "Adds D language support"; 344 plugin.info._version = "0.x.x"; //TODO: fill out automatically 345 plugin.info.author = "Denis Feklushkin <denis.feklushkin@gmail.com>"; 346 347 try 348 GEANY_PLUGIN_REGISTER(plugin, 225); 349 catch(Exception e) 350 nothrowLog!"fatal"(e.msg); 351 }